import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class VendingMachineTest {

	VendingMachine myVendingMachine;
	VendingMachineItem snickers;
	VendingMachineItem coke;
	VendingMachineItem skittles;
	VendingMachineItem mtDew;
	double myCash;
	
	@Before
	public void setUp() throws Exception {
		myVendingMachine = new VendingMachine();
		snickers = new VendingMachineItem("snickers", 1.25);
		coke = new VendingMachineItem("coke", 1.50);
		skittles = new VendingMachineItem("skittles", 0.75);
		mtDew = new VendingMachineItem("mt dew", 1.50);
	}

	@After
	public void tearDown() throws Exception {
		myVendingMachine = null;
		snickers = null;
		coke = null;
		skittles = null;
		mtDew = null;
	}

	/****
	 *  AddItem Method tests
	 ****/
	
	/*
	 * This will test to make sure that Slot A accepts
	 * a VendingMachineItem and will return the same
	 * item 
	 */
	@Test
	public void testAddItem_SlotA() {
		myVendingMachine.addItem(snickers, VendingMachine.A_CODE);		
		assertEquals(snickers, myVendingMachine.getItem(VendingMachine.A_CODE));
	}
	
	/*
	 * This will test to make sure that Slot B accepts
	 * a VendingMachineItem and will return the same
	 * item 
	 */
	@Test
	public void testAddItem_SlotB() {
		myVendingMachine.addItem(coke, VendingMachine.B_CODE);		
		assertEquals(coke, myVendingMachine.getItem(VendingMachine.B_CODE));
	}

	/*
	 * This will test to make sure that Slot C accepts
	 * a VendingMachineItem and will return the same
	 * item 
	 */
	@Test
	public void testAddItem_SlotC() {
		myVendingMachine.addItem(skittles, VendingMachine.C_CODE);		
		assertEquals(skittles, myVendingMachine.getItem(VendingMachine.C_CODE));
	}

	/*
	 * This will test to make sure that Slot D accepts
	 * a VendingMachineItem and will return the same
	 * item 
	 */
	@Test
	public void testAddItem_SlotD() {
		myVendingMachine.addItem(mtDew, VendingMachine.D_CODE);		
		assertEquals(mtDew, myVendingMachine.getItem(VendingMachine.D_CODE));
	}
	
	/*
	 * This will test to make sure that an error is 
	 * thrown when a non existent slot is specified
	 */
	@Test (expected=VendingMachineException.class)
	public void testAddItem_SlotE() throws Exception {
		myVendingMachine.addItem(mtDew, "E");
	}

	/*
	 * This will test to make sure that an error is 
	 * thrown when a non existent slot is specified
	 */
	@Test (expected=VendingMachineException.class)
	public void testAddItem_SlotNull() throws Exception {
		myVendingMachine.addItem(mtDew, null);
	}

	/*
	 * This will test to make sure that an error is 
	 * thrown when a invalid item is passed
	 */
	@Test (expected=VendingMachineException.class)
	public void testAddItem_ItemNull() throws Exception {
		myVendingMachine.addItem(null, myVendingMachine.A_CODE);
	}
	
	
	/*
	 * This will test to make sure that an error is 
	 * thrown when a slot has already been filled.
	 */
	@Test (expected=VendingMachineException.class)
	public void testAddItem_SlotAFilled() throws Exception {
		myVendingMachine.addItem(skittles, VendingMachine.A_CODE);
		myVendingMachine.addItem(coke, VendingMachine.A_CODE);
	}
		

	/****
	 *  RemoveItem Method tests
	 ****/
	
	/*
	 * This will test to make sure that a VendingMachineItem
	 * can be removed from Slot A in the myVendingMachine object
	 */
	@Test
	public void testRemoveItem_SlotA() {
		myVendingMachine.addItem(mtDew, VendingMachine.A_CODE);
		assertEquals(mtDew, myVendingMachine.removeItem(VendingMachine.A_CODE));
	}

	/*
	 * This will test to make sure that a VendingMachineItem
	 * can be removed from Slot B in the myVendingMachine object
	 */
	@Test
	public void testRemoveItem_SlotB() {
		myVendingMachine.addItem(skittles, VendingMachine.B_CODE);
		assertEquals(skittles, myVendingMachine.removeItem(VendingMachine.B_CODE));
	}

	/*
	 * This will test to make sure that a VendingMachineItem
	 * can be removed from Slot C in the myVendingMachine object
	 */
	@Test
	public void testRemoveItem_SlotC() {
		myVendingMachine.addItem(mtDew, VendingMachine.C_CODE);
		assertEquals(mtDew, myVendingMachine.removeItem(VendingMachine.C_CODE));;
	}

	/*
	 * This will test to make sure that a VendingMachineItem
	 * can be removed from Slot D in the myVendingMachine object
	 */
	@Test
	public void testRemoveItem_SlotD() {

		myVendingMachine.addItem(mtDew, VendingMachine.D_CODE);
		assertEquals(mtDew, myVendingMachine.removeItem(VendingMachine.D_CODE));
	}
	
	/*
	 * This will test to make sure that an error is 
	 * thrown when a non existent slot is specified
	 */
	@Test (expected=VendingMachineException.class)
	public void testRemoveItem_SlotE() {
		myVendingMachine.removeItem("E");
	}
	
	/*
	 * This will test to make sure that an error is 
	 * thrown when a slot has already been emptied.
	 */
	@Test (expected=VendingMachineException.class)
	public void testRemoveItem_SlotAEmpty() {
		myVendingMachine.removeItem(VendingMachine.A_CODE);
	}
	
	
	/****
	 *  InsertMoney Method tests
	 ****/
	
	/*
	 * This will test to make sure the InsertMoney method 
	 * accepts an amount of money and returns that amount
	 */
	@Test
	public void testInsertMoney() {
		myCash = 1.00;
		myVendingMachine.insertMoney(myCash);
		assertEquals(myCash, myVendingMachine.getBalance(), 0.00);
	}

	/*
	 * This will test to make sure the InsertMoney method 
	 * does not accept a negative amount of money
	 */
	@Test (expected=VendingMachineException.class)
	public void testInsertMoney_Negative() {
		myCash = -1.00;
		myVendingMachine.insertMoney(myCash);
	}

	/*
	 * This will test to make sure the InsertMoney method 
	 * does not change the balance if 0 is inserted
	 */
	@Test
	public void testInsertMoney_NoValue() {
		myCash = 0.00;
		myVendingMachine.insertMoney(myCash);
		assertEquals(myCash, myVendingMachine.getBalance(), 0.00);
	}

	
	/****
	 *  GetBalance Method tests
	 ****/
	
	/*
	 * This will test to make sure the beginning balance is 0
	 */
	@Test
	public void testGetBalance_Start() {
		assertEquals(0.00, myVendingMachine.getBalance(), 0.000);
	}
	
	/*
	 * This will test to make sure the balance returns the amount
	 * of money inserted.
	 */
	@Test
	public void testGetBalance_AfterInsert() {
		myVendingMachine.insertMoney(1.00);
		assertEquals(1.00, myVendingMachine.getBalance(), 0.000);
	}
	
	/*
	 * This will test to make sure the balance returns the amount
	 * of money inserted of different amounts (coin + dollar)
	 */
	@Test
	public void testGetBalance_AfterInsertCombination() {
		myVendingMachine.insertMoney(0.25);
		myVendingMachine.insertMoney(1.00);
		assertEquals(1.25, myVendingMachine.getBalance(), 0.000);
	}

	/*
	 * This will test to make sure the balance returns the amount
	 * of money after a purchase.
	 */
	@Test
	public void testGetBalance_AfterPurchase() {
		myVendingMachine.insertMoney(2.00);
		myVendingMachine.addItem(coke, VendingMachine.A_CODE);
		myVendingMachine.makePurchase(VendingMachine.A_CODE);
		assertEquals(0.50, myVendingMachine.getBalance(), 0.000);
	}


	/****
	 *  MakePurchase Method tests
	 ****/
	
	/*
	 * This will test that the method returns false when the balance is 0
	 */
	@Test
	public void testMakePurchase_NoMoney() {
		myVendingMachine.addItem(skittles, VendingMachine.C_CODE);
		assertFalse(myVendingMachine.makePurchase(VendingMachine.C_CODE));
	}

	/*
	 * This will test the method returns false when the balance is to low
	 */
	@Test
	public void testMakePurchase_LowBalance() {
		myVendingMachine.addItem(skittles, VendingMachine.C_CODE);
		myVendingMachine.insertMoney(0.50);
		assertFalse(myVendingMachine.makePurchase(VendingMachine.C_CODE));
	}

	/*
	 * This will test the method returns false when the balance is to low
	 */
	@Test
	public void testMakePurchase_CorrectAmount() {
		myVendingMachine.addItem(mtDew, VendingMachine.A_CODE);
		myVendingMachine.insertMoney(1.50);
		assertTrue(myVendingMachine.makePurchase(VendingMachine.A_CODE));
	}

	/*
	 * This will test the method returns false when the slot is empty
	 */
	@Test (expected=VendingMachineException.class)
	public void testMakePurchase_SlotE() {
		myVendingMachine.addItem(skittles, VendingMachine.A_CODE);
		myVendingMachine.insertMoney(1.50);
		myVendingMachine.makePurchase("E");
	}
	
	/*
	 * This will test the method returns false when the slot is empty
	 */
	@Test (expected=VendingMachineException.class)
	public void testMakePurchase_SlotAEmpty() {
		myVendingMachine.insertMoney(1.50);
		assertFalse(myVendingMachine.makePurchase(VendingMachine.A_CODE));
	}

	
	/****
	 * ReturnChange Method tests
	 ****/
	
	/*
	 * This will test the ReturnChange method when the balance is 0
	 */
	@Test
	public void testReturnChange_NoBalance() {
		assertEquals(0.00, myVendingMachine.returnChange(), 0.000);
	}
	
	/*
	 * This will test the ReturnChange method to make sure it returns
	 * the correct amount
	 */
	@Test
	public void testReturnChange_WithBalance() {
		myVendingMachine.addItem(snickers, VendingMachine.A_CODE);
		myVendingMachine.insertMoney(2.00);
		myVendingMachine.makePurchase(VendingMachine.A_CODE);
		assertEquals(0.75, myVendingMachine.returnChange(), 0.000);
	}
	
	/*
	 * This will test the ReturnChange method to make sure that the 
	 * balance is now 0 with a purchase
	 */
	@Test
	public void testReturnChange_CheckZeroBalancePurchase() {
		myVendingMachine.addItem(snickers, VendingMachine.A_CODE);
		myVendingMachine.insertMoney(1.25);
		myVendingMachine.makePurchase(VendingMachine.A_CODE);
		myVendingMachine.returnChange();
		assertEquals(0.00, myVendingMachine.getBalance(), 0.000);
	}

	/*
	 * This will test the ReturnChange method to make sure that the 
	 * balance is now 0 without purchasing
	 */
	@Test
	public void testReturnChange_CheckZeroBalanceNoPurchase() {
		myVendingMachine.addItem(snickers, VendingMachine.A_CODE);
		myVendingMachine.insertMoney(1.25);
		myVendingMachine.returnChange();
		assertEquals(0.00, myVendingMachine.getBalance(), 0.000);
	}

}
